/*
 * Copyright (C) 2010-2014 Enrique Lara (enrique.posix2.lara@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package app.transittool.model;

import android.app.AlarmManager;
import android.app.PendingIntent;

import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;

import android.database.Cursor;

import android.media.Ringtone;
import android.media.RingtoneManager;

import android.net.Uri;

import android.util.Log;

import app.transittool.Android;
import app.transittool.PollingReceiver;
import app.transittool.PollingService;
import app.transittool.R;

import app.transittool.provider.Projections;
import app.transittool.provider.Tables.Alarms;

import app.transittool.util.Constants;

import app.transittool.view.DaysOfWeek;

import java.lang.ref.SoftReference;
import java.util.Calendar;

public abstract class AbstractAlarm implements Alarm {
    private static final long MILLIS_PER_SECOND = 1000L;
    private static final long SECONDS_PER_MINUTE = 60L;
    private static final String TAG = "AbstractAlarm";
    private final Uri uri;
    private final Context context;
    private final ContentValues dirty = new ContentValues();

    //
    private int id = -1;
    private boolean enabled = false;
    private int hour = -1;
    private int minute = -1;
    private int repeat = 0;

    //
    private int agencyId = -1;
    private String stopCode;
    private String routeCode;
    private String direction;
    private int maxTime = -1;
    private int minTime = -1;

    //
    private String label;
    private String ringtone;
    private boolean vibrate = false;
    private int imageId = R.drawable.emotes_face_smile;

    // Cached
    private SoftReference<Agency> agencyRef = null;

//    private SoftReference<Stop> stopRef = null;
    private SoftReference<Route> routeRef = null;

    protected AbstractAlarm(final Cursor cursor, final Context context) {
        this.context = context;
        id = cursor.getInt(0);
        uri = ContentUris.withAppendedId(Alarms.CONTENT_URI, id);
        init(cursor);
    }

    @Override
    public Agency getAgency() {
        Agency a = null;

        if (agencyRef != null) {
            a = agencyRef.get();
        }

        if (a == null) {
            a = App.getAgency(agencyId, context);
            agencyRef = new SoftReference<Agency>(a);
        }

        return a;
    }

    @Override
    public Uri getUri() {
        return uri;
    }

    @Override
    public int getId() {
        return id;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    @Override
    public int getHour() {
        return hour;
    }

    @Override
    public int getMinute() {
        return minute;
    }

    @Override
    public int getRepeat() {
        return repeat;
    }

    @Override
    public String getTimeStr() {
        final Calendar firstTime = Calendar.getInstance();
        firstTime.set(Calendar.HOUR_OF_DAY, getHour());
        firstTime.set(Calendar.MINUTE, getMinute());

        return Android.formatDate(context, firstTime.getTime()).toString();
    }

    //
    @Override
    public String getStopCode() {
        return (stopCode != null) ? stopCode : "";
    }

    @Override
    public String getRouteCode() {
        return routeCode;
    }

    @Override
    public Route getRoute() {
        Route r = null;

        if (routeRef != null) {
            r = routeRef.get();
        }

        if (r == null) {
            r = getAgency().getRouteByCode(getRouteCode());

            if (r != null) {
                routeRef = new SoftReference<Route>(r);
            }
        }

        return r;
    }

    @Override
    public String getDirection() {
        return direction;
    }

    @Override
    public int getMaxTime() {
        return maxTime;
    }

    @Override
    public int getMinTime() {
        return minTime;
    }

    //
    @Override
    public String getStopName() {
        Stop s = null;

        try {
            s = getAgency().getStopByCode(getStopCode());
        } catch (final DataRetrievalException e) {
            Log.w(TAG, "Problems data for stop:" + getStopCode(), e);
        }

        return (s != null) ? s.getName() : getStopCode();
    }

    @Override
    public abstract String getDirectionName();

    @Override
    public String getLabelOrStopName() {
        if (label != null) {
            return label;
        } else {
            return getStopName();
        }
    }

    @Override
    public String getLabel() {
        return label;
    }

    @Override
    public String getRingtone() {
        return ringtone;
    }

    @Override
    public Ringtone getRingtoneObj() {
        final String ringtone2 = getRingtone();

        if ((ringtone2 == null) || (ringtone2.trim().length() == 0)) {
            return null;
        }

        final Uri ringtoneUri = Uri.parse(ringtone2);

        return RingtoneManager.getRingtone(context, ringtoneUri);
    }

    @Override
    public boolean isVibrate() {
        return vibrate;
    }

    @Override
    public int getImageId() {
        return imageId;
    }

    @Override
    public void setEnabled(final boolean enabled) {
        dirty.put(Alarms.ENABLED, enabled ? 1 : 0);
        this.enabled = enabled;
    }

    @Override
    public void setRepeat(final int repeat) {
        dirty.put(Alarms.DAYS_IN_WEEK, repeat);
        this.repeat = repeat;
    }

    @Override
    public void setHour(final int hour) {
        dirty.put(Alarms.HOUR, hour);
        this.hour = hour;
    }

    @Override
    public void setMinute(final int minute) {
        dirty.put(Alarms.MINUTE, minute);
        this.minute = minute;
    }

    //
    @Override
    public void setStop(final String stationCode) {
        dirty.put(Alarms.STOP, stationCode);
        stopCode = stationCode;

//        stopRef = null;
    }

    @Override
    public void setRoute(final String line) {
        dirty.put(Alarms.ROUTE, line);
        routeCode = line;
        routeRef = null;
    }

    @Override
    public void setDirection(final String direction) {
        dirty.put(Alarms.DIRECTION, direction);
        this.direction = direction;
    }

    @Override
    public void setMaxTime(final int maxTime) {
        dirty.put(Alarms.TIME_MAX, maxTime);
        this.maxTime = maxTime;
    }

    @Override
    public void setMinTime(final int minTime) {
        dirty.put(Alarms.TIME_MIN, minTime);
        this.minTime = minTime;
    }

    //
    @Override
    public void setLabel(final String label) {
        dirty.put(Alarms.LABEL, label);
        this.label = label;
    }

    @Override
    public void setRingtone(final String ringtone) {
        dirty.put(Alarms.RINGTONE, ringtone);
        this.ringtone = ringtone;
    }

    @Override
    public void setVibrate(final boolean vibrate) {
        dirty.put(Alarms.VIBRATE, vibrate ? 1 : 0);
        this.vibrate = vibrate;
    }

    @Override
    public void setImageId(final int resourceId) {
        dirty.put(Alarms.IMAGE, resourceId);
        imageId = resourceId;
    }

    @Override
    public boolean gotoView() {
        final Intent intent = new Intent(Constants.ACTION_VIEW, uri);
        context.startActivity(intent);

        return true;
    }

    @Override
    public boolean gotoViewNearbyStops() {
        final Intent intent = new Intent(Constants.ACTION_NEARBY_STOPS, uri);
        context.startActivity(intent);

        return true;
    }

    @Override
    public boolean gotoEdit() {
        return getAgency().gotoEdit(this.getUri());
    }

    @Override
    public boolean isDirty() {
        return dirty.size() > 0;
    }

    @Override
    public boolean updateQuiet() {
        return doUpdate(true);
    }

    @Override
    public boolean update() {
        return doUpdate(false);
    }

    private synchronized boolean doUpdate(final boolean quiet) {
        boolean result = true;

        cancelAlarm();

        if (dirty.size() > 0) {
            result = context.getContentResolver().update(uri, dirty, null, null) == 1;

            if (result) {
                dirty.clear();
            }
        }

        if (this.isEnabled()) {
            setAlarm(quiet);
        }

        return result;
    }

    @Override
    public boolean delete() {
        cancelAlarm();

        return context.getContentResolver().delete(uri, null, null) == 1;
    }

    @Override
    public boolean startPollingNow() {
        final Intent intent = new Intent(Constants.ACTION_POLLING_ONE_OFF, uri, context, PollingService.class);
        context.startService(intent);

        return true;
    }

    private void startPollingAt(final long startTime) {
        final Intent intent = new Intent(Constants.ACTION_POLLING_ONE_OFF, uri, context, PollingReceiver.class);
        final PendingIntent sender = PendingIntent.getBroadcast(context, 0, intent, 0);

        final AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        am.set(AlarmManager.RTC_WAKEUP, startTime, sender);
    }

    @Override
    public boolean startPollingIn(final int minutes) {
        final long startTime = System.currentTimeMillis() + (minutes * SECONDS_PER_MINUTE * MILLIS_PER_SECOND);
        startPollingAt(startTime);

        return true;
    }

    /**
     * Schedule the alarm with theS Alarm Manager.
     *
     * <pre>
     * Alarm - Alarm Time as displayed in the UI.
     * Min - Minimum lead time.
     * Max - Maximum lead time, Also start of polling period.
     *
     * TBefore - before the alarm and polling period.
     * TPoll - within the polling period window.
     * TClose - after the normal polling period, but still before the alarm time.
     * TAfter - after the alarm.
     *
     * --------TBefore-------------------|-TPoll---|-TClose-|---TAfter---
     * ----------------------------------|---------|--------|------------
     * ----------------------------------*---------*--------*------------
     * ----------------------------------|---------|--------|------------
     * ----------------------------------|---------|--------Alarm--------
     * ----------------------------------|---------Min-------------------
     * ----------------------------------Max-----------------------------
     * </pre>
     */
    private void setAlarm(final boolean quiet) {
        // Alarm time as displayed in the UI
        final Calendar alarmTime = Calendar.getInstance();
        alarmTime.set(Calendar.HOUR_OF_DAY, getHour());
        alarmTime.set(Calendar.MINUTE, getMinute());
        alarmTime.set(Calendar.SECOND, 0);
        alarmTime.set(Calendar.MILLISECOND, 0);

        // If alarmTime is in past, add a day.
        final Calendar now = Calendar.getInstance();

        if (now.after(alarmTime)) {
            alarmTime.add(Calendar.DAY_OF_YEAR, 1);
        }

        final Calendar firstTime = Calendar.getInstance();
        firstTime.setTime(alarmTime.getTime());
        firstTime.add(Calendar.MINUTE, -1 * getMaxTime());

        if (repeat != 0) {
            final Intent intent = new Intent(Constants.ACTION_POLLING, uri, context, PollingReceiver.class);
            final PendingIntent sender = PendingIntent.getBroadcast(context, 0, intent, 0);

            // Register alarm with AlarmManager.
            // Alarm Manager will run intent immediately if firstTime is in the
            // past.
            final AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
            am.setRepeating(AlarmManager.RTC_WAKEUP, firstTime.getTimeInMillis(), AlarmManager.INTERVAL_DAY, sender);

            if (!quiet) {
                if (firstTime.getTimeInMillis() <= System.currentTimeMillis()) {
                    Android.showLongToast(context, R.string.alarm_occurs_within, getMaxTime());
                } else {
                    popAlarmSet(alarmTime, R.string.alarm_set);
                }
            }
        } else {
            if (firstTime.getTimeInMillis() <= System.currentTimeMillis()) {
                // TPoll or TClose, fire off now
                startPollingNow();

                if (!quiet) {
                    Android.showLongToast(context, R.string.alarm_occurs_within, getMaxTime());
                }
            } else {
                startPollingAt(firstTime.getTimeInMillis());

                if (!quiet) {
                    popAlarmSet(alarmTime, R.string.alarm_set_for);
                }
            }
        }
    }

    private void popAlarmSet(final Calendar alarmTime, final int msgId) {
        final CharSequence timeStr = Android.formatDate(context, alarmTime.getTime());
        Android.showLongToast(context, msgId, timeStr);
    }

    @Override
    public void cancelAlarm() {
        final AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

        Intent intent = new Intent(Constants.ACTION_POLLING, uri, context, PollingReceiver.class);
        PendingIntent sender = PendingIntent.getBroadcast(context, 0, intent, 0);
        am.cancel(sender);

        intent = new Intent(Constants.ACTION_POLLING_ONE_OFF, uri, context, PollingReceiver.class);
        sender = PendingIntent.getBroadcast(context, 0, intent, 0);
        am.cancel(sender);
    }

    private synchronized boolean init(final Cursor cursor) {
        /* Timing */
        enabled = cursor.getInt(Projections.AlarmsAll.ENABLED) == 1;
        hour = cursor.getInt(Projections.AlarmsAll.HOUR);
        minute = cursor.getInt(Projections.AlarmsAll.MINUTE);
        repeat = cursor.getInt(Projections.AlarmsAll.DAYS_IN_WEEK);
        agencyId = cursor.getInt(Projections.AlarmsAll.AGENCY_ID);

        /* Criteria */
        stopCode = cursor.getString(Projections.AlarmsAll.STOP);
        routeCode = cursor.getString(Projections.AlarmsAll.ROUTE);
        direction = cursor.getString(Projections.AlarmsAll.DIRECTION);
        maxTime = cursor.getInt(Projections.AlarmsAll.TIME_MAX);
        minTime = cursor.getInt(Projections.AlarmsAll.TIME_MIN);
        /* Representation */
        label = cursor.getString(Projections.AlarmsAll.LABEL);
        ringtone = cursor.getString(Projections.AlarmsAll.RINGTONE);
        vibrate = cursor.getInt(Projections.AlarmsAll.VIBRATE) == 1;
        imageId = cursor.getInt(Projections.AlarmsAll.IMAGE);

        dirty.clear();

        return true;
    }

    @Override
    public String getRepeatStr() {
        return new DaysOfWeek(repeat).getRepeatStr();
    }

    @Override
    public boolean isActiveToday() {
        return new DaysOfWeek(repeat).isActiveToday();
    }

    @Override
    public boolean isSilent() {
        return (getRingtone() == null) || (getRingtone().trim().length() == 0);
    }
}
